﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;
using Bling.WPF;
using Bling.DSL;
using Bling.Geom;
using Bling.Util;
using Bling.Properties;

namespace Bling {
  public static class ShaderLib {
    public static readonly ImageBrush CloudImage = new ImageBrush(new BitmapImage("Resources/clouds.png".MakePackUri(typeof(ShaderLib))));
    public static readonly ImageBrush NoiseImage = new ImageBrush(new BitmapImage("Resources/noise.png".MakePackUri(typeof(ShaderLib))));
    public static readonly ImageBrush SinCosATanImage = new ImageBrush(new BitmapImage("Resources/sincosatan.png".MakePackUri(typeof(ShaderLib))));
    public static readonly ImageBrush RainbowImage = new ImageBrush(new BitmapImage("Resources/rainbow.png".MakePackUri(typeof(ShaderLib))));


    public static PointBl CloudPoint(PointBl uv, DoubleBl div, DoubleBl RandomSeed) {
      PointBl offset = ((Point4DBl)CloudImage.Bl()[uv.X / div, (uv.Y / div + 0.9d.Bl().Min(RandomSeed)).Frac]).XY * 2.0 - 1.0.Bl();
      return offset;
    }

    public static ColorBl Water(BrushBl Input, BrushBl OldInput, PointBl uv, DoubleBl Progress, DoubleBl RandomSeed) {
      PointBl offset = CloudPoint(uv, 10, RandomSeed);
      ColorBl c1 = OldInput[(uv + offset * Progress).Frac];
      ColorBl c2 = Input[uv];
      return (c1.ScA <= 0.0).Condition(c2, Progress.Lerp(c1, c2));
    }
    public static ColorBl Blood(BrushBl Input, BrushBl OldInput, PointBl uv, DoubleBl Progress, DoubleBl RandomSeed) {
      DoubleBl offset = (Progress + Progress * CloudImage.Bl()[new PointBl(uv.X, RandomSeed)].ScR).Min(1.0);
      uv = new PointBl(uv.X, uv.Y - offset);
      return (uv.Y > 0.0).Condition(OldInput[uv], Input[uv.Frac]);
    }
    public static ColorBl Crumple(BrushBl Input, BrushBl OldInput, PointBl uv, DoubleBl Progress, DoubleBl RandomSeed) {
      PointBl offset = CloudPoint(uv, 5, RandomSeed);
      DoubleBl p = Progress * 2d;
      p = (p > 1.0).Condition(1.0 - (p - 1.0), p);
      ColorBl c1 = OldInput[(uv + offset * p).Frac];
      ColorBl c2 = Input[(uv + offset * p).Frac];
      return Progress.Lerp(c1, c2);
    }
    public static ColorBl RotateCrumple(BrushBl Input, BrushBl OldInput, PointBl uv, DoubleBl Progress, DoubleBl RandomSeed) {
      PointBl offset = CloudPoint(uv, 10, RandomSeed);
      PointBl center = uv + offset / 10.0;
      PointBl toUV = uv - center;
      DoubleBl len = toUV.Length;
      PointBl normToUV = toUV / len;
      RadianAngleBl angle = normToUV.SimpleAngle;
      angle += 1d.ToPI() * 2d * Progress;
      PointBl newOffset = angle.ToPoint() * len;

      ColorBl c1 = OldInput[(center + newOffset).Frac];
      ColorBl c2 = Input[(center + newOffset).Frac];
      return Progress.Lerp(c1, c2);
    }
    public static ColorBl RotateWiggle(BrushBl Input, BrushBl OldInput, PointBl uv, DoubleBl Progress, DoubleBl RandomSeed) {
      PointBl center = new PointBl(.5, .5);
      PointBl toUV = uv - center;
      DoubleBl distanceFromCenter = toUV.Length;
      PointBl normalToUV = toUV / distanceFromCenter;
      DoubleBl angle = (normalToUV.SimpleAngle + 1d.ToPI()) / (2d.ToPI());
      DoubleBl offset1 = ((Point4DBl) CloudImage.Bl()[angle, (Progress / 2 + distanceFromCenter / 5 + RandomSeed).Frac]).X * 2.0 - 1.0;
      DoubleBl offset2 = offset1 * 2.0 * 0.3.Bl().Min(1 - Progress) * distanceFromCenter;
      offset1 = offset1 * 2.0 * 0.3.Bl().Min(Progress) * distanceFromCenter;
      ColorBl c1 = OldInput[(center + normalToUV * (distanceFromCenter + offset1)).Frac];
      ColorBl c2 = Input[(center + normalToUV * (distanceFromCenter + offset2)).Frac];
      return Progress.Lerp(c1, c2);
    }


    /// <summary>
    /// Ripple effect.
    /// </summary>
    /// <param name="Input">Texture that effect will be applied to.</param>
    /// <param name="uv">Current pixel position being shaded.</param>
    /// <param name="Center">Center of ripple as a percentage-relative point in texture ((0,0) to (1,1)).</param>
    /// <param name="Amplitude">Amplitude of ripple between 0 and 1.</param>
    /// <param name="Frequency">Frequency of ripple between 0 and 100.</param>
    /// <param name="Phase">Phase of ripple between 0 and 10.</param>
    /// <returns>Color of pixel for desired ripple.</returns>
    public static ColorBl Ripple(BrushBl Input, PointBl uv, PointBl Center, DoubleBl Amplitude, DoubleBl Frequency, DoubleBl Phase) {
      PointBl toPixel = uv - Center;
      DoubleBl distance = toPixel.Length;
      PointBl direction = toPixel / distance;
      RadianAngleBl angle = direction.SimpleAngle;
      PointBl wave = new RadianAngleBl(Frequency * distance + Phase).ToPoint();
      DoubleBl falloff = (1d - distance).Saturate.Square();

      distance += Amplitude * wave.X * falloff;
      PointBl uv2 = Center + distance * direction;
      DoubleBl lighting = (wave.Y * falloff).Saturate * 0.2 + 0.8;

      ColorBl color = Input[uv2];
      color = ColorBl.FromScRGB(color.ScA, color.ScRGB * Amplitude.Lerp(1d, lighting));
      return color;
    }
  }
}